#include "TrackBallManipulator.h"
#include "../Camera.h"
namespace GCL {
TrackBallManipulator::TrackBallManipulator(Camera *camera)
    :Manipulator(camera)
{
    rotate_speed_ = 0.02;
    zoom_speed_ = 0.05;
}

void TrackBallManipulator::rotate(int posX, int posY)
{

    Vec3 upDirection,sideDirection;
    this->computeUpAndSideDirection(upDirection,sideDirection);
    Vec3 eye = this->getCamera()->get_direction();
    Vec3 moveDirection = sideDirection * (posX - last_posX_) + upDirection * (posY - last_posY_);
    Scalar angle = moveDirection.length();
    moveDirection.Normalize();

    Vec3 axis = eye ^ moveDirection;
    axis.Normalize();
    Quat quat(axis, rotate_speed_ * angle / this->getCamera()->distance());
    getCamera()->rotation_matrix() *= quat.convertToMatrix();
}

void TrackBallManipulator::pan(int posX, int posY)
{
    Vec3 hitOld;
    int hit0 = hitCenterPlane(last_posX_,last_posY_, hitOld);
    Vec3 hitNew;
    int hit1 = hitCenterPlane(posX,posY,hitNew);
    if(hit0 && hit1)
    {
        getCamera()->move(hitNew - hitOld);
    }

}




void TrackBallManipulator::computeUpAndSideDirection(Vec3 &upDirection, Vec3 &sideDirection)
{
    Vec4i viewport = this->getCamera()->get_viewport();
    Vec3  direction = this->getCamera()->get_direction();
    Vec3  center = this->getCamera()->center();
    Vec3 rayPoint,rayDir, intersect0, intersect1, intersect2;


    viewport[0] = viewport[0] + viewport[2] / 2;
    viewport[1] = viewport[1] + viewport[3] / 2;

    this->getCamera()->get_project_ray(viewport[0],viewport[1],rayPoint,rayDir);
    Vec3::getIntersectionRayToPlane(rayPoint,rayDir,center,direction,intersect0);

    this->getCamera()->get_project_ray(viewport[0] + 1,viewport[1],rayPoint,rayDir);
    Vec3::getIntersectionRayToPlane(rayPoint,rayDir,center,direction,intersect1);


    this->getCamera()->get_project_ray(viewport[0],viewport[1] + 1,rayPoint,rayDir);
    Vec3::getIntersectionRayToPlane(rayPoint,rayDir,center,direction,intersect2);

    upDirection = intersect2 - intersect0;
    upDirection.Normalize();

    sideDirection = intersect1 - intersect0;
    sideDirection.Normalize();


}
Scalar TrackBallManipulator::speed() const
{
    return rotate_speed_;
}

void TrackBallManipulator::setSpeed(const Scalar &speed)
{
    rotate_speed_ = speed;
}

int TrackBallManipulator::mouseMove(int posX, int posY)
{
    if(getModifier() == NoModifier )
    {
        if(getButton() == LeftButton)
        {
            rotate(posX,posY);
        }
        else if(getButton() == RightButton)
        {
            pan(posX,posY);
        }
        last_posX_ = posX;
        last_posY_ = posY;
    }
    return EventHandler::mouseMove(posX,posY);
}

int TrackBallManipulator::mousePress(int posX, int posY, unsigned int button, unsigned int modifier)
{
    if(modifier == NoModifier)
    {
        last_posX_ = posX;
        last_posY_ = posY;
    }
    return EventHandler::mousePress(posX,posY,button,modifier);
}

int TrackBallManipulator::mouseRelease(int posX, int posY, unsigned int button, unsigned int modifier)
{
    if(getModifier() == NoModifier)
    {
        last_posX_ = -1;
        last_posY_ = -1;
    }
    return EventHandler::mouseRelease(posX,posY,button,modifier);
}

int TrackBallManipulator::wheel(const float &delta)
{
    Scalar scale =  (1 + ((delta > 0)?1:-1)*zoom_speed_ );
    getCamera()->set_position(getCamera()->center() +
                              (getCamera()->position() - getCamera()->center()) * scale);

    return 0;
}

int TrackBallManipulator::hitCenterPlane(int posX, int posY, Vec3 &intersect)
{

    Vec3 center = getCamera()->center();
    Vec3 np = getCamera()->get_direction();
    Vec3 rayPoint,rayDir;
    getCamera()->get_project_ray(posX,posY,rayPoint,rayDir);
    rayDir.Normalize();
    return Vec3::getIntersectionRayToPlane(rayPoint,rayDir,center,np,intersect);
}


}
